﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace YawetagLib.Utilities
{
    public static class TypeResolver
    {
        private static readonly object _lock = new object();

        static TypeResolver()
        {
            RegisteredTypes = new HashSet<Type>();
        }

        public static void Register(Type type)
        {
            CheckForConcreteType(type);

            lock (_lock)
            {
                if (!RegisteredTypes.Contains(type))
                {
                    RegisteredTypes.Add(type);
                }
            }
        }

        public static void Register(Type interfaceType, Assembly assembly)
        {
            foreach (Type type in assembly.GetTypes().Where(AssignableClassLookup(interfaceType)))
            {
                Register(type);
            }
        }

        public static void UnRegister(Type type)
        {
            CheckForConcreteType(type);

            lock (_lock)
            {
                if (RegisteredTypes.Contains(type))
                {
                    RegisteredTypes.Remove(type);
                }
            }
        }

        public static void UnRegister(Type interfaceType, Assembly assembly)
        {
            foreach (Type type in assembly.GetTypes().Where(AssignableClassLookup(interfaceType)))
            {
                UnRegister(type);
            }
        }

        private static void CheckForConcreteType(Type type)
        {
            if (type.IsInterface || type.IsAbstract)
            {
                throw new TypeResolvingException(string.Format("The type {0} could not get registered, it cannot be an interface or abstract class", type.Name));
            }
        }

        public static Type Get(Type type)
        {
            if (type.IsInterface)
            {
                Type resultType = GetRegisteredType(type) ?? GetCurrentDomainType(type);

                if (resultType != null)
                {
                    return resultType;
                }

                throw new TypeResolvingException(string.Format("There is no concrete implementation for the interface {0} in the current Domain.", type.Name));
            }

            return type;
        }

        private static Type GetRegisteredType(Type interfaceType)
        {
            return RegisteredTypes.Where(interfaceType.IsAssignableFrom).SingleOrDefault();
        }

        private static Type GetCurrentDomainType(Type interfaceType)
        {
            Type localType = interfaceType.Assembly.GetTypes()
                .Where(AssignableClassLookup(interfaceType))
                .FirstOrDefault();

            if (localType == null)
            {
                Assembly assembly = AppDomain.CurrentDomain.GetAssemblies()
                    .Where(a => a.GetTypes().Any(AssignableClassLookup(interfaceType)))
                    .FirstOrDefault();

                return assembly != null
                    ? assembly.GetTypes().Where(AssignableClassLookup(interfaceType)).FirstOrDefault() 
                    : null;
            }

            return localType;
        }

        private static Func<Type, bool> AssignableClassLookup(Type interfaceType)
        {
            return i => i.IsClass && !i.IsAbstract && interfaceType.IsAssignableFrom(i);
        }

        private static ICollection<Type> RegisteredTypes { get; set; }
    }
}
